package misc

import akka.actor.{Actor, ActorSystem, Props}
import akka.routing.RoundRobinPool
import com.typesafe.config.ConfigFactory

import scala.util.Random

/**
  * Created by shichen on 2017/9/6.
  *
  * 比较纯scala程序与akka性能优劣
  * https://stackoverflow.com/questions/41744457/akka-worse-performance-with-more-actors\
  *
  * 有空把测试调整一下，最好能用图形展示结果
  */

sealed trait PiMessage
case object Calculate extends PiMessage
case class Work(iter: Int) extends PiMessage
case class Result(value: Int) extends PiMessage

class Master(workerNum :Int, iterNum: Int) extends Actor {
  val startTime :Double = System.currentTimeMillis()
  val workerRouter = context.actorOf(
    Props[Worker].withRouter(RoundRobinPool(workerNum)),name = "workerRouter")
  var quadSum: Int = _
  var numResults: Int = _

  override def receive: Receive = {
    case Calculate => for (i <- 0 to workerNum) workerRouter ! Work(iterNum/workerNum)
    case Result(value) =>
      quadSum += value
      numResults += 1
      if (numResults == workerNum) {
        val pi = (4.0*quadSum)/iterNum
        val duration = System.currentTimeMillis() - startTime
        println(s"workerNum=$workerNum, iter=$iterNum, pi=$pi, duration=$duration ms")
        context.stop(self)
      }
  }
}

class Worker extends Actor {
  override def receive: Receive = {
    case Work(iter) => sender() ! Result(calculatePi(iter))
  }

  private def calculatePi(iter: Int):Int = {
    //val r = Random //这里有问题！ actor线程共用了scala.util.Random的seed
    val r = new Random
    var res = 0
    for (i <- 0 to iter) {
      val x = r.nextFloat()
      val y = r.nextFloat()
      if (((x * x) + (y * y)) < 1.0) {
        res += 1
      }
    }
    res
  }
}

object MCpi {
  def main(args: Array[String]): Unit = {
    for (i <- 0 to 10) {
      pureScalaTest(args)

    }
    //actorTest(args)
  }

  def actorTest(args: Array[String]): Unit = {
    val iterations = args(1).toInt
    val workerNum = args(0).toInt

    val conf = ConfigFactory.parseString("akka.remote.netty.tcp.port=2555")
      .withFallback(ConfigFactory.load())

    val sys = ActorSystem("actor-test",conf)
    val master = sys.actorOf(Props(new Master(workerNum = workerNum, iterNum = iterations)),"master")
    println("staring master, send Calculate")
    master ! Calculate

    Thread.sleep(3000)
    sys.terminate()
  }

  def pureScalaTest(args: Array[String]): Unit = {
    //Define the number of iterations to perform
    val iterations = args(1).toInt

    //Get the current time
    val start = System.currentTimeMillis()


    // Create random number generator
    val r = scala.util.Random
    //Store number of points within circle
    var inQuadrant: Int = 0

    for (i <- 0 to iterations) {
      //Generate random point
      val X = r.nextFloat()
      val Y = r.nextFloat()

      //Determine whether or not the point is within the circle
      if (((X * X) + (Y * Y)) < 1.0)
        inQuadrant += 1
    }
    //Calculate pi
    val pi = (4.0 * inQuadrant) / iterations
    //Get the total time
    val time = System.currentTimeMillis() - start
    println(s"iter=$iterations, Pi =$pi, duration =$time ")
  }
}
